<?php
/**
 * wap支付宝支付接口
 * author fengwei
 * 
 */

class alipaywapPaymentDriver extends PaymentDriver{
	
	private $Gateway_ssl = 'http://wappaygw.alipay.com/service/rest.htm?';
	private $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
	private $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
	private $sign_type = 'MD5';
	private $is_notify = null;
	private $transport = 'http';
	
	public function CreateLink($payment, $parameter)
	{
		$parameter['name'] = preg_replace('/\&[a-z]{2,4}\;/i', '', $parameter['name']);
		$parameter['detail'] = str_replace(array('"',"'",'\\','&'), '', $parameter['detail']);
		$merchant_url = ini('settings.site_url')."/?mod=me";
		$call_back_url = ini('settings.site_url')."/?mod=cbu";
		//$parameter['notify_url'] ="";
		$req_id = date('Ymdhis');
		$req_data = '<direct_trade_create_req><notify_url>' . $parameter['notify_url'] . '</notify_url><call_back_url>' . $call_back_url . '</call_back_url><seller_account_name>' . $payment['config']['account'] . '</seller_account_name><out_trade_no>' . $parameter['sign'] . '</out_trade_no><subject>' . $parameter['name'] . '</subject><total_fee>' . $parameter['price'] . '</total_fee><merchant_url>' . $merchant_url . '</merchant_url></direct_trade_create_req>';
		$post = array(
				'service'           => $payment['config']['service'],
				"partner" 			=> $payment['config']['partner'],
				'sec_id'      		=> 'MD5',
				'format'			=> 'xml',
				'v'				    => '2.0',
				'req_id'			=> $req_id,
				'req_data'	=> $req_data,
				'_input_charset'	=> ini('settings.charset')
		);
		return $this->__BuildForm($payment, $post);
	}
	
	public function CreateConfirmLink($payment, $order)
	{
		if ($payment['config']['service'] == 'create_direct_pay_by_user' || $this->isDirectPay($payment, $order['orderid']))
		{
			return '?mod=buy&code=tradeconfirm&id='.$order['orderid'];
		}
		else
		{
			$paylog = logic('pay')->GetLog($order['orderid'], 0, '1', true);
			return 'http://lab.alipay.com/consume/record/buyerConfirmTrade.htm?tradeNo='.$paylog['trade_no'];
		}
	}
	
	public function CallbackVerify($payment)
	{
		if ($this->__Is_Nofity())
		{
			
			
			$trade_status = $this->__Notify_Verify($payment);
		}
		else
		{	
			$trade_status = $this->__Return_Verify($payment);
		}
		return $this->__Trade_Status($trade_status);
	}
	
	public function GetTradeData()
	{
		$src = ($this->__Is_Nofity()) ? 'POST' : 'GET';
		$trade = array();
		 if ($src=='GET') {
			$trade['sign'] = logic('safe')->Vars($src, 'out_trade_no', 'number');
			$trade['trade_no'] = logic('safe')->Vars($src, 'trade_no', 'number');
			$trade['price'] = logic('safe')->Vars($src, 'total_fee', 'float');
			$trade['money'] = $trade['price'];
			$trade['status'] = $this->__Trade_Status(logic('safe')->Vars($src, 'trade_status', 'txt'));
		}else{ 
			$notify_data = post('notify_data');
			$doc = new DOMDocument();
			$doc->loadXML($notify_data);
			$trade['sign'] = $doc->getElementsByTagName( "out_trade_no" )->item(0)->nodeValue;;
			$trade['trade_no'] = $doc->getElementsByTagName( "trade_no" )->item(0)->nodeValue;
			$trade['price'] = $doc->getElementsByTagName( "total_fee" )->item(0)->nodeValue;
			$trade['money'] = $trade['price'];
			$trade['status'] = $doc->getElementsByTagName( "trade_status" )->item(0)->nodeValue;
		}
		
		return $trade;
	}
	
	public function StatusProcesser($status)
	{
		if (!$this->__Is_Nofity())
		{
			return false;
		}
		if ($status != 'VERIFY_FAILED')
		{
			echo 'success';
		}
		else
		{
			echo 'failed';
		}
		return true;
	}
	
	public function GoodSender($payment, $express, $sign, $type)
	{
		if ($payment['config']['service'] == 'create_direct_pay_by_user' || $this->isDirectPay($payment, $sign))
		{
			if ($type == 'ticket')
			{
				logic('callback')->Bridge($sign)->Processed($sign, 'TRADE_FINISHED');
			}
			else
			{
				logic('callback')->Bridge($sign)->Processed($sign, 'WAIT_BUYER_CONFIRM_GOODS');
			}
			return;
		}
		$post = array(
				'service'           => 'send_goods_confirm_by_platform',
				'partner'           => $payment['config']['partner'],
				'_input_charset'    => ini('settings.charset'),
				'trade_no'			=> $express['trade_no'],
				'logistics_name'    => $express['name'],
				'invoice_no'		=> $express['invoice'],
				'transport_type'    => 'EXPRESS',
		);
		$url = $this->__BuildURL($payment, $post);
	
		$this->__SrvGET($url);
		return;
	}
	
	private function __Trade_Status($trade_status)
	{
		return ($trade_status == 'TRADE_SUCCESS') ? 'TRADE_FINISHED' : $trade_status;
	}
	
	private function isDirectPay($payment, $sign)
	{
		$directPay = false;
		if ($payment['config']['service'] == 'trade_create_by_buyer')
		{
			$order = logic('order')->SrcOne($sign);
			$paylog = logic('pay')->GetLog($order['orderid'], $order['userid']);
			$directPay = (count($paylog) == 3 && $paylog[0]['status'] == 'TRADE_FINISHED');
		}
		return $directPay;
	}
	
	private function __Is_Nofity()
	{
		if (is_null($this->is_notify))
		{
			if (post('trade_status'))
			{
				$this->is_notify = true;
			}
			else
			{
				$this->is_notify = false;
			}
		}
		return $this->is_notify;
	}
	
	private function __BuildForm($payment, $parameter)
	{
		$request_data = $this->__CreateSign($payment, $parameter);
		$sResult = $this->getHttpResponsePOST($this->Gateway_ssl, '',$request_data,ini('settings.charset'));
		$html_text = urldecode($sResult);
		//解析远程模拟提交后返回的信息
		$para_html_text = $this->parseResponse($html_text);
		
		//获取request_token
		$request_token = $para_html_text['request_token'];
		$req_data = '<auth_and_execute_req><request_token>' . $request_token . '</request_token></auth_and_execute_req>';
		
		$param = array(
				"service" => "alipay.wap.auth.authAndExecute",
				"partner" => trim($request_data['partner']),
				'sec_id'  => 'MD5',
				"format"	=> 'xml',
				"v"	=> '2.0',
				"req_id"	=> $request_data['req_id'],
				"req_data"	=> $req_data,
				"_input_charset"	=> trim(strtolower(ini('settings.charset')))
		);
		$para = $this->__CreateSign($payment, $param);
		$sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->Gateway_ssl."_input_charset=".trim(strtolower(ini('settings.charset')))."' method='get' >";
		if($_SERVER['HTTP_USER_AGENT'] && preg_match("/(iphone|android)/i",$_SERVER['HTTP_USER_AGENT'],$match))
		{
			if($match[0] == 'iPhone')
			{
				$sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->Gateway_ssl."_input_charset=".trim(strtolower(ini('settings.charset')))."' method='get' TARGET='alipayframe'>";
			}
			
		}
		
		while (list ($key, $val) = each ($para)) {
			$sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>";
		}
		
		//submit按钮控件请不要含有name属性
		$sHtml = $sHtml."<input type='submit' value='点击跳转到支付宝支付' onclick='javascript:$.hook.call(\"pay.button.click\");' ></form>";
		//$sHtml.= '<IFRAME  id="iframepage" frameborder="0" scrolling="no"  name="alipayframe" width="100%"  height="4000px"></IFRAME>';
		//$sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>";
		
		return $sHtml;
	}
	
	private function __BuildURL($payment, $parameter)
	{
		$sign = $this->__CreateSign($payment, $parameter);
		$parameter = $this->__arg_sort($parameter);
		$arg = $this->__create_linkstring_urlencode($parameter);
		$parameter['sign'] = $sign;
		if($parameter['service'] != 'alipay.wap.trade.create.direct' && $parameter['service'] != 'alipay.wap.auth.authAndExecute') {
			$parameter['sign_type'] = strtoupper(trim($this->sign_type));
		}
		$sResult = $this->getHttpResponsePOST(Gateway_ssl, '',$parameter,ini('settings.charset'));
		$url = $this->Gateway_ssl.$arg.'&sign='.$sign.'&sign_type='.'MD5';
		return $url;
	}
	
	private function __Return_Verify($payment)
	{	
		if(empty($_GET)) {//判断GET来的数组是否为空
			return false;
		}else {
			$sign = get('sign', 'txt');
			$array=array(
			
					"out_trade_no" => get("out_trade_no"),
			
					"request_token" => get("request_token"),
			
					"result" => get("result"),
			
					"trade_no" => get("trade_no"),
			
					"sign" => get("sign"),
			
					"sign_type" => get("sign_type"),
			
			);
			$para = $this->__para_filter($array);
			$para = $this->__arg_sort($para);
			$prestr = $this->__create_linkstring($para);
			$isSgin = false;
			$isSgin = $this->md5Verify($prestr, $sign, $payment['config']['key']);
		}
	
		if ($isSgin)
		{
			return 'TRADE_SUCCESS';
		}
		else
		{
			return 'VERIFY_FAILED';
		}
	}
	
	private function __Notify_Verify($payment)
	{
		$doc = new DOMDocument();
		$doc->loadXML(post('notify_data'));
		$notify_id = $doc->getElementsByTagName( "notify_id" )->item(0)->nodeValue;
		 if($this->transport == 'http')
		{
			$url = $this->http_verify_url
			.'service=notify_verify'
			.'&partner='.$payment['config']['partner']
			.'&notify_id='.$notify_id;
		}
		else
		{
			$url = $this->https_verify_url
			.'partner='.$payment['config']['partner']
			.'&notify_id='.$notify_id;
		} 
		
		
		$result = $this->__Verify($url);
		$sign = post('sign', 'txt');
		$parameter = $this->__para_filter($_POST);
		//$parameter = $this->__arg_sort($parameter);
		$para = $this->sortNotifyPara($parameter);
		$prestr = $this->__create_linkstring($para);
		$isSgin = false;
		$isSgin = $this->md5Verify($prestr, $sign, $payment['config']['key']);
		
	
		if ($isSgin)
		{
			return post('trade_status', 'txt');
		}
		else
		{
			return 'VERIFY_FAILED';
		}
	}
	
	private function __Verify($url, $time_out = '60')
	{
		$urlArr     = parse_url($url);
		$errNo      = '';
		$errStr     = '';
		$transPorts = '';
		if($urlArr['scheme'] == 'https')
		{
			$transPorts = 'ssl://';
			$urlArr['port'] = '443';
		}
		else
		{
			$transPorts = 'tcp://';
			$urlArr['port'] = '80';
		}
		$fp = msockopen($transPorts . $urlArr['host'], $urlArr['port'], $errNo, $errStr, $time_out);
		if(!$fp)
		{
			zlog('error')->found('error.msockopen');
			die("ERROR: $errNo - $errStr<br />\n");
		}
		else
		{
			fputs($fp, "POST ".$urlArr["path"]." HTTP/1.1\r\n");
			fputs($fp, "Host: ".$urlArr["host"]."\r\n");
			fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
			fputs($fp, "Content-length: ".strlen($urlArr["query"])."\r\n");
			fputs($fp, "Connection: close\r\n\r\n");
			fputs($fp, $urlArr["query"] . "\r\n\r\n");
			while(!feof($fp))
			{
				$info[]=@fgets($fp, 1024);
			}
			fclose($fp);
			$info = implode(",",$info);
			return $info;
		}
	}
	
	private function __SrvGET($url, $time_out = '60')
	{
		$urlArr     = parse_url($url);
		$errNo      = '';
		$errStr     = '';
		$transPorts = '';
		if($urlArr['scheme'] == 'https')
		{
			$transPorts = 'ssl://';
			$urlArr['port'] = '443';
		}
		else
		{
			$transPorts = 'tcp://';
			$urlArr['port'] = '80';
		}
		$fp = msockopen($transPorts . $urlArr['host'], $urlArr['port'], $errNo, $errStr, $time_out);
		if(!$fp)
		{
			zlog('error')->found('error.msockopen');
			die("ERROR: $errNo - $errStr<br />\n");
		}
		else
		{
			fputs($fp, "GET ".$urlArr["path"]."?".$urlArr["query"]." HTTP/1.1\r\n");
			fputs($fp, "Host: ".$urlArr["host"]."\r\n");
			fputs($fp, "Connection: close\r\n\r\n");
			while(!feof($fp))
			{
				$info[]=@fgets($fp, 1024);
			}
			fclose($fp);
			$info = implode(",",$info);
			return $info;
		}
	}
	
	private function __CreateSign($payment, $parameter)
	{
		$parameter = $this->__para_filter($parameter);
		$parameter = $this->__arg_sort($parameter);
		$string = $this->__create_linkstring($parameter);		
		$string .= $payment['config']['key'];
		$sign = md5($string);
		$parameter['sign'] = $sign;
		if($parameter['service'] != 'alipay.wap.trade.create.direct' && $parameter['service'] != 'alipay.wap.auth.authAndExecute') {
			$parameter['sign_type'] = strtoupper(trim($this->sign_type));
		}
		return $parameter;
		
		/* $mysign = "";
		 switch (strtoupper(trim($this->sign_type))) {
		case "MD5" :
		$mysign = md5Sign($prestr, $this->alipay_config['key']);
		break;
		case "RSA" :
		$mysign = rsaSign($prestr, $this->alipay_config['private_key_path']);
		break;
		case "0001" :
		$mysign = rsaSign($prestr, $this->alipay_config['private_key_path']);
		break;
		default :
		$mysign = "";
		}
		
		return $mysign; */
	}
	private function __create_linkstring($array)
	{
		$arg  = '';
		foreach ($array as $key => $val)
		{
			$arg .= $key.'='.$val.'&';
		}
		$arg = substr($arg, 0, count($arg)-2);
		return $arg;
	}
	private function __create_linkstring_urlencode($array)
	{
		$arg  = '';
		foreach ($array as $key => $val)
		{
			if ($key != 'service' && $key != '_input_charset')
			{
				$arg .= $key.'='.urlencode($val).'&';
			}
			else
			{
				$arg .= $key.'='.$val.'&';
			}
		}
		$arg = substr($arg, 0, count($arg)-2);
		return $arg;
	}
	private function __arg_sort($array)
	{
		ksort($array);
		reset($array);
		return $array;
	}
	private function __para_filter($parameter)
	{
		$ignores = array(
				'sign' => 1,
				'sign_type' => 1,
				'mod' => 1,
				'pid' => 1
		);
		$para = array();
		foreach ($parameter as $key => $val)
		{
			if(isset($ignores[$key]) || $val == '')
			{
				continue;
			}
			else
			{
				$para[$key] = $val;
			}
		}
		return $para;
	}
	
	/**
	 * 签名字符串
	 * @param $prestr 需要签名的字符串
	 * @param $key 私钥
	 * return 签名结果
	 */
	function md5Sign($prestr, $key) {
		$prestr = $prestr . $key;
		return md5($prestr);
	}
	
	/**
	 * 验证签名
	 * @param $prestr 需要签名的字符串
	 * @param $sign 签名结果
	 * @param $key 私钥
	 * return 签名结果
	 */
	function md5Verify($prestr, $sign, $key) {
		$prestr = $prestr . $key;
		$mysign = md5($prestr);

		if($mysign == $sign) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * 异步通知时，对参数做固定排序
	 * @param $para 排序前的参数组
	 * @return 排序后的参数组
	 */
	function sortNotifyPara($para) {
		$para_sort['service'] = $para['service'];
		$para_sort['v'] = $para['v'];
		$para_sort['sec_id'] = $para['sec_id'];
		$para_sort['notify_data'] = $para['notify_data'];
		return $para_sort;
	}
	
	/**
	 * 远程获取数据，POST模式
	 * 注意：
	 * 1.使用Crul需要修改服务器中php.ini文件的设置，找到php_curl.dll去掉前面的";"就行了
	 * 2.文件夹中cacert.pem是SSL证书请保证其路径有效，目前默认路径是：getcwd().'\\cacert.pem'
	 * @param $url 指定URL完整路径地址
	 * @param $cacert_url 指定当前工作目录绝对路径
	 * @param $para 请求的数据
	 * @param $input_charset 编码格式。默认值：空值
	 * return 远程输出的数据
	 */
	function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') {
	
		if (trim($input_charset) != '') {
			$url = $url."_input_charset=".$input_charset;
		}
		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
		curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
		curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
		curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
		curl_setopt($curl,CURLOPT_POST,true); // post传输数据
		curl_setopt($curl,CURLOPT_POSTFIELDS,$para);// post传输数据
		$responseText = curl_exec($curl);
		//var_dump( curl_error($curl) );exit;//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
		curl_close($curl);
	
		return $responseText;
	}
	
	/**
	 * 解析远程模拟提交后返回的信息
	 * @param $str_text 要解析的字符串
	 * @return 解析结果
	 */
	function parseResponse($str_text) {
		//以“&”字符切割字符串
		$para_split = explode('&',$str_text);
		//把切割后的字符串数组变成变量与数值组合的数组
		foreach ($para_split as $item) {
			//获得第一个=字符的位置
			$nPos = strpos($item,'=');
			//获得字符串长度
			$nLen = strlen($item);
			//获得变量名
			$key = substr($item,0,$nPos);
			//获得数值
			$value = substr($item,$nPos+1,$nLen-$nPos-1);
			//放入数组中
			$para_text[$key] = $value;
		}
	
		if( ! empty ($para_text['res_data'])) {
			//解析加密部分字符串
			if($this->alipay_config['sign_type'] == '0001') {
				$para_text['res_data'] = rsaDecrypt($para_text['res_data'], $this->alipay_config['private_key_path']);
			}
				
			//token从res_data中解析出来（也就是说res_data中已经包含token的内容）
			$doc = new DOMDocument();
			$doc->loadXML($para_text['res_data']);
			$para_text['request_token'] = $doc->getElementsByTagName( "request_token" )->item(0)->nodeValue;
		}
	
		return $para_text;
	}
	
}